#include "network.h"

#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

void zeroAddress(struct sockaddr_in * address)
{
	socklen_t socketAddressSize = sizeof(struct sockaddr_in);
	bzero(address, socketAddressSize);
}

int createSocketUDP(struct Socket * s, uint16_t port)
{
	zeroAddress(&(s->address));
	s->address.sin_family = AF_INET;
	s->address.sin_addr.s_addr = INADDR_ANY;
	s->address.sin_port = htons(port);
	
	s->fileDescriptor = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	const int enableBroadcast = 1;
	setsockopt(s->fileDescriptor, SOL_SOCKET, SO_BROADCAST, &enableBroadcast, sizeof(enableBroadcast));
	
	if(s->fileDescriptor < 0)
	{
		printf("ERROR: Cannot create unicast UDP socket on port %d\n", port);
		return 1;
	}
	else
	{
		return 0;
	}
}

int bindSocket(struct Socket * socket)
{
	if(bind(socket->fileDescriptor, (struct sockaddr *)&(socket->address), sizeof(socket->address)))
	{
		printf("ERROR: Cannot bind UDP port %d\n", ntohs(socket->address.sin_port));
		return 1;
	}
	else
	{
		return 0;
	}
}

void closeSocket(struct Socket * socket)
{
	close(socket->fileDescriptor);
}

void setSocketNonBlocking(struct Socket * socket)
{
	int flags = fcntl(socket->fileDescriptor, F_GETFL, O_ACCMODE);
	flags |= O_NONBLOCK;
	fcntl(socket->fileDescriptor, F_SETFL, flags);
}

int broadcastPacket(struct Socket * socket, struct Packet * packet, uint16_t port)
{
	struct sockaddr_in packetAddress;
	zeroAddress(&packetAddress);
	packetAddress.sin_family = AF_INET;
	packetAddress.sin_addr.s_addr = INADDR_BROADCAST;
	packetAddress.sin_port = htons(port);
	
	ssize_t size = sendto(socket->fileDescriptor, packet->buffer, packet->size, 0, (struct sockaddr *)&packetAddress, sizeof(packetAddress));
	if(size < 1)
	{
		printf("ERROR: Cannot broadcast packet from port %d\n", ntohs(socket->address.sin_port));
		return 1;
	}
	else
	{
		return 0;
	}
}

int sendPacket(struct Socket * socket, struct Packet * packet, struct Address * address)
{
	struct sockaddr_in packetAddress;
	zeroAddress(&packetAddress);
	packetAddress.sin_family = AF_INET;
	packetAddress.sin_addr.s_addr = htonl(address->address);
	packetAddress.sin_port = htons(address->port);
	
	ssize_t size = sendto(socket->fileDescriptor, packet->buffer, packet->size, 0, (struct sockaddr *)&packetAddress, sizeof(packetAddress));
	if(size < 1)
	{
		printf("ERROR: Cannot send packet from port %d to ", ntohs(socket->address.sin_port));
		printAddress(address);
		printf("\n");
		return 1;
	}
	else
	{
		return 0;
	}
}

int receivePacket(struct Socket * socket, struct Packet * packet, struct Address * address)
{
	struct sockaddr_in datagramAddress;
	zeroAddress(&datagramAddress);
	socklen_t datagramAddressSize = sizeof(datagramAddress);
	
	packet->size = recvfrom(socket->fileDescriptor, packet->buffer, PACKET_BUFFER_MAX_SIZE, 0, (struct sockaddr *)&datagramAddress, &datagramAddressSize);
	if(packet->size < 1)
	{
		printf("ERROR: Failed to receive packet from port %d\n", ntohs(socket->address.sin_port));
		return 1;
	}
	else
	{
		address->address = ntohl(datagramAddress.sin_addr.s_addr);
		address->port = ntohs(datagramAddress.sin_port);
		return 0;
	}
}




int sendStream(struct Socket * socket, struct Stream * stream, struct Address * address)
{
	int result = 0;
	struct Packet packet;
	size_t size;
	
	while(0 == result && 0 < stream->size)
	{
		if(stream->size < PACKET_BUFFER_MAX_SIZE)
		{
			size = stream->size;
		}
		else
		{
			size = PACKET_BUFFER_MAX_SIZE;
		}
		memcpy(packet.buffer, stream->data, size);
		packet.size = size;
		
		result = sendPacket(socket, &packet, address);
		
		if(0 == result)
		{
			pop_front(stream, size);
		}
	}
	return result;
}

int receiveStream(struct Socket * socket, struct Stream * stream, struct Address * address)
{
	struct Packet packet;
	
	int result = receivePacket(socket, &packet, address);
	if(0 == result)
	{
		push_back(stream, packet.size, packet.buffer);
	}
	
	return result;
}





int setAddress(struct Address * address, const char * addressString, uint16_t port)
{
	struct sockaddr_in socketAddress;
	bzero(&socketAddress, sizeof(socketAddress));
	socketAddress.sin_family = AF_INET;
	
	if(1 != inet_pton(AF_INET, addressString, (void *)&socketAddress.sin_addr))
	{
		printf("ERROR: Invalid address %s\n", addressString);
		return 1;
	}
	else
	{
		address->address = ntohl(socketAddress.sin_addr.s_addr);
		address->port = port;
		return 0;
	}
}

int setAddressFromBoundSocket(struct Address * address, struct Socket * socket)
{
	struct sockaddr_in socketAddress;
	bzero(&socketAddress, sizeof(socketAddress));
	socklen_t socketAddressSize = sizeof(socketAddress);
	
	if(0 != getsockname(socket->fileDescriptor, (struct sockaddr *)&socketAddress, &socketAddressSize))
	{
		printf("ERROR: Cannot get Socket information\n");
		return 1;
	}
	else
	{
		address->address = ntohl(socketAddress.sin_addr.s_addr);
		address->port = ntohs(socketAddress.sin_port);
		return 0;
	}
}

void printAddress(struct Address * address)
{
	printf(
		"%d.%d.%d.%d:%d",
		((unsigned char *)&(address->address))[3],
		((unsigned char *)&(address->address))[2],
		((unsigned char *)&(address->address))[1],
		((unsigned char *)&(address->address))[0],
		address->port
	);
}

int compareAddress(struct Address * address1, struct Address * address2)
{
	if(address1->address == address2->address && address1->port == address2->port)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

